package com.atguigu.tingshu.common.login;

import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * 自定义注解切面类
 *
 * @author: atguigu
 * @create: 2023-12-11 10:38
 */
@Slf4j
@Aspect
@Component
public class GuiGuLoginAspect {

    @Autowired
    private RedisTemplate redisTemplate;


    /**
     * 环绕通知：对所有业务模块api包下使用自定义注解方法进行增强，增强逻辑：
     * 1.自定义注解需要必须登录-根据请求头token查询Redis中用户信息，如果用户信息为空则抛异常，将用户ID存入ThreadLocal
     * 2.自定义注解不需要登录（可登录，可不登录）如果用户登录，将用户ID存入ThreadLocal
     *
     * @param pjp
     * @param guiGuLogin
     * @return
     * @throws Throwable
     */
    @Around("execution(* com.atguigu.tingshu.*.api.*.*(..)) && @annotation(guiGuLogin)")
    public Object doBasicProfiling(ProceedingJoinPoint pjp, GuiGuLogin guiGuLogin) throws Throwable {
        //一、前置通知
        log.info("前缀通知执行了");
        //1.获取前端提交Token
        //1.1 调用请求上下文对象(封装请求、响应对象)，获取请求对象；由SpringMVC前端控制器父类FrameworkServlet处理各种请求，封装req,resp请求对象放入ThreadLocal
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        //将RequestAttributes接口转为实现类：ServletRequestAttributes
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
        HttpServletRequest request = servletRequestAttributes.getRequest();
        //1.2 获取请求头：token对应值
        String token = request.getHeader("token");

        //2.根据token查询存放到Redis中用户信息
        String loginKey = RedisConstant.USER_LOGIN_KEY_PREFIX + token;
        UserInfoVo userInfoVo = (UserInfoVo) redisTemplate.opsForValue().get(loginKey);

        //3.判断自定义注解中是否必须登录，如果必须登录且用户信息为空 则抛出异常
        if (guiGuLogin.required() && userInfoVo == null) {
            //必须抛出业务状态码208异常，前端根据208跳转登录页面
            throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
        }
        //4.判断用户信息有值，将用户ID存入ThreadLocal
        if (userInfoVo != null) {
            AuthContextHolder.setUserId(userInfoVo.getId());
        }
        Object retVal;
        try {
            //二、调用目标方法（某个api包下使用自定义注解方法被调用）
            retVal = pjp.proceed();
        } finally {
            //三、后置通知
            log.info("后缀通知执行了");
            //5.将ThreadLocal中用户信息移除
            AuthContextHolder.removeUserId();
        }
        return retVal;
    }

}
